package tr.edu.ege.kipler.satisfiability.completion;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import tr.edu.ege.kipler.satisfiability.ReasonerException;
import tr.edu.ege.kipler.satisfiability.Reasoner;
import tr.edu.ege.kipler.satisfiability.blocking.BlockingStrategy;
import tr.edu.ege.kipler.satisfiability.blocking.SHOIQDoubleBlocking;
import tr.edu.ege.kipler.satisfiability.completion.rules.CompletionRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.EmptyDomainRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.ABoxRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.AllValuesRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.AtLeastRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.AtMostRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.ChooseRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.IntersectionRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.LazyUnfoldingRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.NegatedConceptDefinitionRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.NegatedRoleInclusionRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.NominalGuessRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.PositiveConceptDefinitionRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.SHOIQMergeStrategy;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.SameNominalMergeRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.SomeValuesRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.TransitivityRule;
import tr.edu.ege.kipler.satisfiability.completion.rules.dl.UnionRule;
import tr.edu.ege.kipler.satisfiability.container.Individual;
import tr.edu.ege.kipler.satisfiability.container.SHOIQABox;
import tr.edu.ege.kipler.satisfiability.container.World;
import tr.edu.ege.kipler.satisfiability.container.WorldConstraintSystem;
import tr.edu.ege.kipler.satisfiability.container.operational.FilterType;
import tr.edu.ege.kipler.satisfiability.container.operational.RelatedIndividualWithTypeFilter;
import tr.edu.ege.kipler.satisfiability.container.operational.RelatedIndividualWithTypeFilterFactory;
import tr.edu.ege.kipler.satisfiability.container.operational.SHOIQIndividualComparator;

/**
 * 
 * @author İnanç Seylan
 *
 */
public class SHOIQBasicCompletionStrategy implements CompletionStrategy {

	private List<ABoxRule> highestPriorityRuleList = new ArrayList<ABoxRule>();

	private List<ABoxRule> middlePriorityRuleList = new ArrayList<ABoxRule>();

	List<ABoxRule> lowestPriorityRuleList = new ArrayList<ABoxRule>();

	private Reasoner reasoner;

	private World currentWorld;

	private WorldConstraintSystem currentWcs;

	public SHOIQBasicCompletionStrategy(Reasoner reasoner) {
		setReasoner(reasoner);
		BlockingStrategy blocking = new SHOIQDoubleBlocking();
		RelatedIndividualWithTypeFilter filter = RelatedIndividualWithTypeFilterFactory
				.getFilter(FilterType.TYPED_SAFE_NEIGHBOR);
		// SameNominalMergeRule is applied with highest priority.
		highestPriorityRuleList.add(new SameNominalMergeRule());
		// Next NominalGuessRule and AtMostRule are applied for nominals.
		middlePriorityRuleList.add(new NominalGuessRule(getReasoner()));
		middlePriorityRuleList.add(new AtMostRule(getReasoner(), blocking,
				new SHOIQMergeStrategy()));
		// All other rules are applied with a lower priority.
		lowestPriorityRuleList.add(new LazyUnfoldingRule());
		lowestPriorityRuleList.add(new PositiveConceptDefinitionRule(blocking));
		lowestPriorityRuleList.add(new IntersectionRule(blocking));
		lowestPriorityRuleList.add(new AllValuesRule(blocking));
		lowestPriorityRuleList.add(new TransitivityRule(blocking));
		lowestPriorityRuleList.add(new UnionRule(getReasoner(), blocking));
		lowestPriorityRuleList.add(new ChooseRule(getReasoner(), blocking));
		lowestPriorityRuleList.add(new AtMostRule(getReasoner(), blocking,
				new SHOIQMergeStrategy()));
		lowestPriorityRuleList
				.add(new SomeValuesRule(blocking, filter, false));
		lowestPriorityRuleList.add(new AtLeastRule(blocking, filter, false));
	}

	/**
	 * @return the reasoner
	 */
	public Reasoner getReasoner() {
		return reasoner;
	}

	/**
	 * @param reasoner
	 *            the reasoner to set
	 */
	public void setReasoner(Reasoner reasoner) {
		this.reasoner = reasoner;
	}

	public void apply(WorldConstraintSystem currentWcs) throws ReasonerException {
		currentWorld = currentWcs.getAllWorlds().iterator().next();
		this.currentWcs = currentWcs;
		CompletionRule compRule = new NegatedRoleInclusionRule(currentWcs,
				currentWorld);
		compRule.apply();
		compRule = new NegatedConceptDefinitionRule(currentWcs, currentWorld);
		compRule.apply();
		compRule = new EmptyDomainRule(currentWcs, currentWorld);
		compRule.apply();
		do {
			applyHighestPriorityRules();
		} while (applyMiddlePriorityRules() || applyLowestPriorityRules());
	}

	private boolean applyLowestPriorityRules() throws ReasonerException {
		return applyARuleFromRuleList(lowestPriorityRuleList, currentWorld
				.getABox().individualIterator());
	}

	private boolean applyMiddlePriorityRules() throws ReasonerException {
		return applyARuleFromRuleList(middlePriorityRuleList, getNominals()
				.iterator());
	}

	private void applyHighestPriorityRules() throws ReasonerException {
		while (applyARuleFromRuleList(highestPriorityRuleList, getNominals()
				.iterator()))
			;
	}

	private boolean applyARuleFromRuleList(List<ABoxRule> ruleList,
			Iterator<Individual> i) throws ReasonerException {
		boolean ruleApplied = false;
		while (i.hasNext() && !ruleApplied) {
			Individual indv = i.next();
			ruleApplied = applyARuleFromRuleList(ruleList, indv);
		}
		return ruleApplied;
	}

	private boolean applyARuleFromRuleList(List<ABoxRule> ruleList,
			Individual indv) throws ReasonerException {
		boolean ruleApplied = false;
		Iterator<ABoxRule> ruleIter = ruleList.iterator();
		while (ruleIter.hasNext() && !ruleApplied) {
			ABoxRule rule = ruleIter.next();
			rule.setWcs(currentWcs);
			rule.setWorld(currentWorld);
			rule.setIndividual(indv);
			rule.apply();
			ruleApplied = rule.isApplied();
		}
		return ruleApplied;
	}

	/*
	 * I sort nominals according to their level here. This is easier than
	 * keeping a sorted set of nominals because when the level of an individual
	 * that is already in the set is changed, the set does not update its
	 * ordering.
	 */
	private List<Individual> getNominals() {
		SHOIQABox abox = (SHOIQABox) currentWorld.getABox();
		List<Individual> result = new ArrayList<Individual>(abox.getNominals());
		Collections.sort(result, new SHOIQIndividualComparator());
		return result;
	}
}
